/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/listen.c,v 1.10 2002/09/20 19:43:40 bboy Exp $
	listen.c: Routines to get listening addresses, and to bind to those addresses.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"
#if HAVE_NET_IF_H
#  include <net/if.h>
#endif
#  include <sys/ioctl.h>

/* Listening sockets (TCP/UDP) */
int *udp_fd = (int *)NULL;
int *tcp_fd = (int *)NULL;

/* Number of listening sockets of each type */
int num_udp_fd = 0;
int num_tcp_fd = 0;


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	LISTENER
	Create server socket, return fd.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
listener(char *ip, int port_number, int protocol, const char *interface)
{
	int fd, opt = 1, port;
	struct sockaddr_in sa;
	char *cp, ipbuf[80];

	/* If "ip" contains a port, let that override the "port" option */
	strncpy(ipbuf, ip, sizeof(ipbuf)-1);
	if ((cp = strchr(ipbuf, ':')))
	{
		if ((port = atoi(cp+1)) <= 0)
			port = port_number;
		*cp = '\0';
	}
	else
		port = port_number;
	strtrim(ipbuf);

	if ((fd = socket(AF_INET, protocol, (protocol == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP)) < 0)
		Err("socket");
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
		Warn("setsockopt");
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);

	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	if (!(inet_aton(ipbuf, &sa.sin_addr)))
		Errx("\"%s\": %s", ipbuf, _("error interpreting IP address"));
	if (bind(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0)
		Err("bind (%s): %s:%d",
			 (protocol == SOCK_STREAM) ? "TCP" : "UDP", inet_ntoa(sa.sin_addr), port);
	if (protocol == SOCK_STREAM)
	{
		if (listen(fd, 5) < 0)
			Err("listen");
	}
	else
	{
		int n, size;
		for (n = 1; n < 1024; n++)
		{
			size = n * 1024;
			if ((setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))) < 0)
				break;
		}
		if (!interface)
		{
#if DEBUG
			Debug(_("listening on %s:%d"), inet_ntoa(sa.sin_addr), port);
#endif
		}
		else
		{
#if DEBUG
			Debug(_("listening on %s:%d (interface %s)"), inet_ntoa(sa.sin_addr), port, interface);
#endif
		}
	}
	return (fd);
}
/*--- listener() --------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	LISTEN_ON_ALL
	Get all interfaces and listen on each.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
listen_on_all(port)
	int port;
{
	struct ifconf ifc;
	int sockfd, numifaces = 32, n;
	u_char *buf;

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		Err("socket");
	if (!(buf = malloc(sizeof(struct ifreq) * numifaces)))
		Err("malloc");

	for (;;)															/* Allocate buffer space */
	{
		ifc.ifc_len = sizeof(struct ifreq) * numifaces;
		ifc.ifc_buf = buf;
		if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
			Err("ioctl(SIOCGIFCONF)");
		if ((ifc.ifc_len / sizeof(struct ifreq)) == numifaces)
		{
			numifaces *= 2;
			if (!(buf = realloc(buf, sizeof(struct ifreq) * numifaces)))
				Err("realloc");
		}
		else break;
	}

	for (n = 0; n < ifc.ifc_len;)
	{
		struct ifreq *ifr = (struct ifreq *)((u_char *)ifc.ifc_req + n);
		struct in_addr addr;
		char ip[80];

#ifdef HAVE_SOCKADDR_SA_LEN
		n += sizeof(ifr->ifr_name) + (ifr->ifr_addr.sa_len > sizeof(struct sockaddr)
												? ifr->ifr_addr.sa_len : sizeof(struct sockaddr));
#else
		n += sizeof(struct ifreq);
#endif /* HAVE_SOCKADDR_SA_LEN */

		if (ifr->ifr_addr.sa_family != AF_INET)			/* Must be AF_INET */
		{
#if DEBUG
			Debug("%s: %s", ifr->ifr_name, _("interface is not AF_INET"));
#endif
			continue;
		}
		if (ifr->ifr_flags & IFF_UP)							/* Must be up */
		{
#if DEBUG
			Debug("%s: %s", ifr->ifr_name, _("interface is down"));
#endif
			continue;
		}
		if (ioctl(sockfd, SIOCGIFADDR, ifr) < 0)			/* Get address */
			Err("ioctl(SIOCGIFADDR)");
		memcpy(&addr, &((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr, sizeof(struct in_addr));
		strncpy(ip, inet_ntoa(addr), sizeof(ip)-1);

		if (!num_udp_fd)
			udp_fd = malloc(sizeof(int));
		else
			udp_fd = realloc(udp_fd, (1+num_udp_fd) * sizeof(int));
		if (!udp_fd)
			Err("malloc");
		udp_fd[num_udp_fd++] = listener(ip, port, SOCK_DGRAM, ifr->ifr_name);
		if (axfr_enabled || tcp_enabled)
		{
			if (!num_tcp_fd)
				tcp_fd = malloc(sizeof(int));
			else
				tcp_fd = realloc(tcp_fd, (1+num_tcp_fd) * sizeof(int));
			if (!tcp_fd)
				Err("malloc");
			tcp_fd[num_tcp_fd++] = listener(ip, port, SOCK_STREAM, ifr->ifr_name);
		}
	}

	close(sockfd);
	free(buf);
}
/*--- listen_on_all() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CREATE_LISTENERS
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
create_listeners(void)
{
	char *bind_opt = conf_get(&Conf, "listen");
	char *port_opt = conf_get(&Conf, "port");
	char *debug_notice = "";
	int port = 53;

	if (port_opt && atoi(port_opt))
		port = atoi(port_opt);

	/* Listening on all addresses - listen on each individually, however */
	if (*bind_opt == '*')
	{
		if (bind_opt[1] == ':' && atoi(bind_opt+2) > 0)
			port = atoi(bind_opt+2);
		listen_on_all(port);
	}
	else
	{
		char *optcopy = xstrdup(bind_opt), *c, *ip;

		/* Replace commas with CONF_FS */
		for (c = optcopy; *c; c++)
			if (*c == ',')
				*c = CONF_FS_CHAR;

		for (c = optcopy; (ip = strsep(&c, CONF_FS_STR)); )
		{
			if (!num_udp_fd)
				udp_fd = malloc(sizeof(int));
			else
				udp_fd = realloc(udp_fd, (1+num_udp_fd) * sizeof(int));
			if (!udp_fd)
				Err("malloc");
			udp_fd[num_udp_fd++] = listener(ip, port, SOCK_DGRAM, NULL);
			if (axfr_enabled || tcp_enabled)
			{
				if (!num_tcp_fd)
					tcp_fd = malloc(sizeof(int));
				else
					tcp_fd = realloc(tcp_fd, (1+num_tcp_fd) * sizeof(int));
				if (!tcp_fd)
					Err("malloc");
				tcp_fd[num_tcp_fd++] = listener(ip, port, SOCK_STREAM, NULL);
			}
		}
		free(optcopy);
	}
#if DEBUG
	debug_notice = _(" (compiled with debug)");
#endif
	Notice("%s %s %s (%d %s)%s", progname, PACKAGE_VERSION, _("ready"),
			 num_udp_fd, num_udp_fd == 1 ? _("interface") : _("interfaces"), debug_notice);
}
/*--- create_listeners() ------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
